# koroFileHeader at Home PC
# Create: 2022-11-12 11:39:36
# LastEdit: 2022-11-20 14:17:29
"""CPP 系列视图"""
__author__ = '749B'


import datetime

from django.db.models import Sum
from django.shortcuts import HttpResponse, render

from pyecharts.components import Table
from pyecharts import options as opts

from utils.cache import cache_auto_key

from .. import models

from typing import List, Tuple

import logging
logger = logging.getLogger(__name__)
logger.success = lambda msg, *args, **kwargs: logger.log(25, msg, *args, **kwargs)  # type: ignore


def cpp_count(request):
    sub_list = models.Subscriptions.objects.values('id', 'name')
    return render(
        request, 
        "billing/cpp_count_table.html", 
        {'sub_list': sub_list},
        )

@cache_auto_key(cache_key='nohash')
def table_cpp_count_cache(
    subid = None,
    days_limit: int = 15,   # 一个月至少有15天（含）的数据，才统计该月
    ) -> Tuple[
        List[Tuple[datetime.date, datetime.date]],  # 第一行的标题行，日期列表原始数据
        List[str],  # 第一列的机型列表，原始数据
        List[List[int]],  # 表格数据，CPP的建议购买数量
        ]:
    """缓存CPP购买建议的查询数据
    之后还有一列已录入的CPP数量，不需要缓存，返回后在视图函数都添加
    date_list 是表格第一行的数据，type_list 是表格第一列的数据
    都要保存原始数据饭后，之后还要根据记录的值追加数据，比如已录入的CPP数量
    展示用的格式在表格展示之前再做修改

    列排序:
        在这个函数中通过date_list实现对列的排序的控制。
        获取到date_list后可任意调整顺序，后续的数据都会对齐。
    列的排序在这里完成，返回到视图函数后不再调整。返回到视图函数后，进行对行的排序
    """
    qs_detail = models.Detail.objects.all()
    if subid:
        qs_detail = qs_detail.filter(subscription=subid)
    # 日期的范围，不超过Detail中已有的日期
    last_date = qs_detail.latest('date').date
    first_date = qs_detail.earliest('date').date

    # 标题行月份
    date_list = []  # [(month_start, month_end), (month_start, month_end), ...]
    next_date = last_date
    # 最多12个月，且不会超出已有的日期范围
    while len(date_list) < 12 and next_date > first_date:
        end_date = next_date
        start_date = end_date.replace(day=1)
        # start_date 不会超过日期范围
        if start_date < first_date:
            start_date = first_date
        # 月中的样本数量要足够
        if (end_date - start_date).days + 1 >= days_limit:
            date_list.append((start_date, end_date))
        next_date = start_date - datetime.timedelta(days=1)
    # 调整为从小到大
    date_list.reverse()

    # 近期使用的虚拟机列表
    last_month_start = (
        last_date.replace(day=1) - datetime.timedelta(days=1)
        ).replace(day=1)
    vm_list = (
        qs_detail
        .filter(
            consumed_service='Microsoft.Compute', meter_category='Virtual Machines',
            date__range=(last_month_start, last_date),
            )
        .exclude(
            # 排除机型，有些机型是无法购买CPP的，比如B系列
            meter_sub_category__in=['BS Series', 'BS Series Windows'],
            )
        .values_list('product', flat=True)
        .distinct()
        )

    vm_month_num = {}  # {'type': {month1_start: 2.96, month2_start: 3.15}, ...]}
    for month_start, month_end in date_list:
        qs = qs_detail.filter(
            consumed_service='Microsoft.Compute', meter_category='Virtual Machines',
            product__in=vm_list,
            date__range=(month_start, month_end),
            ).values('product').annotate(hours=Sum('consumed_quantity'))
        vm_nums = {}  # {'type': 2.96}
        for q in qs:
            type = q['product']
            hours = q['hours']
            # Azure EA 的算法是每月都有 24*31=744小时
            # 我这里有一天算一个24小时，保证每月数量不会因为大小月而浮动
            vm_nums[type] = hours / 24 / (month_end - month_start).days
        for type, num in vm_nums.items():
            vm_month_num.setdefault(type, {})
            vm_month_num[type][month_start] = num

    type_list = []
    month_count_list = []
    for type, month_num_map in vm_month_num.items():
        type_list.append(type)
        count_list = []
        for month_start, _ in date_list:
            num = month_num_map.get(month_start, 0)
            count_list.append(num)
        month_count_list.append(count_list)
    return date_list, type_list, month_count_list

def table_cpp_count(request):
    subid = request.GET.get('subid')
    sub_name = models.Subscriptions.objects.get(id=subid).name if subid else '全部'
    # 从table_cpp_count_cache()返回的数据只能做行的排序调整
    # 对列的顺序调整要在table_cpp_count_cache()里对date_list进行排序
    date_list, type_list, month_count_list = table_cpp_count_cache(subid, days_limit=15)

    # 所有数据的开始和结束时间，之后加到表格的副标题中
    start_date_all = min(min(date_list))
    end_date_all = max(max(date_list))

    # 表格的标题行
    table_headers = ['机型 Virtual Machines'] + [ d[0].strftime("%Y-%m") for d in date_list ]

    table_rows = []
    # 如果有录入的CPP，则加一列
    cpp_counts = models.CppProduct.objects.filter(cpp_count__gt=0)
    if cpp_counts.exists():
        table_headers.append('账号录入数')
        cpp_counts_map = dict(cpp_counts.values_list('product', 'cpp_count'))
        cpp_count_list = []
        for type in type_list:
            cpp_count_list.append(cpp_counts_map.get(type, 0))
        for type, count_list, cpp_count in zip(type_list, month_count_list, cpp_count_list):
            count_list = [ round(n, 1) for n in count_list ]
            # 这里可以去掉类型名称的前缀
            table_rows.append([type.removeprefix('Virtual Machines').strip(), *count_list, cpp_count])
    else:
        for type, count_list in zip(type_list, month_count_list):
            count_list = [ round(n, 1) for n in count_list ]
            table_rows.append([type.removeprefix('Virtual Machines').strip(), *count_list])

    max_month_index = date_list.index(max(date_list)) + 1
    if cpp_counts.exists():
        # 先按CPP的数量，再按最近一个月的数值，从大到小排序
        table_rows.sort(key=lambda x: (x[-1], x[max_month_index]), reverse=True)
    else:
        # 按最近一个月的数值从大到小排序
        table_rows.sort(key=lambda x: x[max_month_index], reverse=True)
    
    table = (
        Table()
        .add(
            headers=table_headers, 
            rows=table_rows,
            )
        .set_global_opts(
            title_opts=opts.ComponentTitleOpts(
                title=f'月度虚拟机使用数量统计 - {sub_name if sub_name else "全部"}', 
                subtitle=f'{start_date_all} - {end_date_all}')
            )
        )
    return HttpResponse(table.render_embed())  
